1use crate::copp::clarabel_backend::{ConstraintsClarabel, ObjConsClarabel};
31use crate::copp::copp3::Topp3Profile;
32#[cfg(any(feature = "c", feature = "python", test))]
33use crate::copp::copp3::Topp3ProfileRef;
34use crate::copp::copp3::formulation::{Copp3Problem, get_weight_a_copp3, get_weight_a_topp3};
35use crate::copp::copp3::opt3::ClarabelExpertInfor3rd;
36use crate::copp::copp3::opt3::clarabel_constraints::{
37 clarabel_standard_capacity_topp3, clarabel_standard_constraint_topp3,
38};
39use crate::copp::{
40 ClarabelOptions, CoppObjective, clarabel_to_copp3_solution, validate_copp3_objectives,
41};
42use crate::diag::{
43 CoppError, DebugVerboser, SilentVerboser, SummaryVerboser, TraceVerboser, Verboser, Verbosity,
44 check_boundary_state_copp3_valid, check_s_interval_valid, format_duration_human,
45};
46use crate::robot::robot_core::{Robot, RobotBasic, RobotTorque};
47use clarabel::algebra::CscMatrix;
48use clarabel::solver::SupportedConeT::{NonnegativeConeT, SecondOrderConeT};
49use clarabel::solver::{DefaultSolution, DefaultSolver, IPSolver, SupportedConeT};
50use core::f64;
51use itertools::{Itertools, izip};
52use nalgebra::{DMatrix, DVectorView};
53
54pub fn copp3_socp<'a, M: RobotTorque>(
75 problem: &Copp3Problem<'a, M>,
76 options: &ClarabelOptions,
77) -> Result<Topp3Profile, CoppError> {
78 let (result, solution) = copp3_socp_expert(problem, options)?;
79 result.ok_or_else(|| CoppError::ClarabelSolverStatus("copp3_socp".into(), solution.status))
80}
81
82pub fn copp3_socp_expert<'a, M: RobotTorque>(
108 problem: &Copp3Problem<'a, M>,
109 options: &ClarabelOptions,
110) -> Result<(Option<Topp3Profile>, DefaultSolution<f64>), CoppError> {
111 let info = copp3_socp_expert_with_info(problem, options)?;
112 let _ = &info.linsolver;
113 Ok((info.result, info.solution))
114}
115
116pub fn copp3_socp_expert_with_info<'a, M: RobotTorque>(
126 problem: &Copp3Problem<'a, M>,
127 options: &ClarabelOptions,
128) -> Result<ClarabelExpertInfor3rd, CoppError> {
129 match options.verbosity() {
130 Verbosity::Silent => copp3_socp_core(problem, (options, SilentVerboser)),
131 Verbosity::Summary => copp3_socp_core(problem, (options, SummaryVerboser::new())),
132 Verbosity::Debug => copp3_socp_core(problem, (options, DebugVerboser::new())),
133 Verbosity::Trace => copp3_socp_core(problem, (options, TraceVerboser::new())),
134 }
135}
136
137fn copp3_socp_core<'a, M: RobotTorque>(
149 problem: &Copp3Problem<'a, M>,
150 options_verboser: (&ClarabelOptions, impl Verboser),
151) -> Result<ClarabelExpertInfor3rd, CoppError> {
152 let (options, mut verboser) = options_verboser;
153 let idx_s_start = problem.idx_s_start;
154 let a_boundary = problem.a_boundary;
155 let b_boundary = problem.b_boundary;
156 let num_stationary = problem.num_stationary;
157 if verboser.is_enabled(Verbosity::Summary) {
158 verboser.record_start_time();
159 }
160 if verboser.is_enabled(Verbosity::Trace) {
161 let settings = options.clarabel_settings();
162 crate::verbosity_log!(
163 crate::diag::Verbosity::Summary,
164 "copp3_socp: options snapshot -> allow(almost={}, max_iter={}, max_time={}, callback_term={}, insufficient_progress={}), tol_gap_rel={}, tol_feas={}, max_iter={}, verbose={}",
165 options.is_allow(clarabel::solver::SolverStatus::AlmostSolved),
166 options.is_allow(clarabel::solver::SolverStatus::MaxIterations),
167 options.is_allow(clarabel::solver::SolverStatus::MaxTime),
168 options.is_allow(clarabel::solver::SolverStatus::CallbackTerminated),
169 options.is_allow(clarabel::solver::SolverStatus::InsufficientProgress),
170 settings.tol_gap_rel,
171 settings.tol_feas,
172 settings.max_iter,
173 settings.verbose
174 );
175 }
176 check_boundary_state_copp3_valid(a_boundary, b_boundary)?;
178 let n = problem.a_linearization.len() - 1;
179 let idx_s_final = idx_s_start + n;
180 if verboser.is_enabled(Verbosity::Summary) {
181 crate::verbosity_log!(
182 crate::diag::Verbosity::Summary,
183 "\ncopp3_socp started: {} <= idx_s <= {}, objectives = {}, s_len = {}.",
184 idx_s_start,
185 idx_s_final,
186 problem.objectives.len(),
187 problem.a_linearization.len()
188 );
189 }
190 check_s_interval_valid("copp3_socp", idx_s_start, idx_s_final)?;
191 validate_copp3_objectives(
192 "copp3_socp",
193 problem.objectives,
194 problem.robot.dim(),
195 problem.a_linearization.len(),
196 )?;
197 let (cap_val_std, cap_b_std, cap_cone_std) =
204 clarabel_standard_capacity_topp3(&problem.robot.constraints, (idx_s_start, idx_s_final));
205 let (cap_val_obj, cap_b_obj, cap_cone_obj, n_vars) =
206 clarabel_objective_capacity_copp3(n, problem.objectives, problem.robot);
207 if verboser.is_enabled(Verbosity::Debug) {
208 crate::verbosity_log!(
209 crate::diag::Verbosity::Summary,
210 "copp3_socp: capacity estimate std(val={cap_val_std}, b={cap_b_std}, cone={cap_cone_std}), obj(val={cap_val_obj}, b={cap_b_obj}, cone={cap_cone_obj}), n_vars={n_vars}."
211 );
212 }
213 let mut row = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
216 let mut col = Vec::<usize>::with_capacity(cap_val_std + cap_val_obj);
217 let mut val = Vec::<f64>::with_capacity(cap_val_std + cap_val_obj);
218 let mut b = Vec::<f64>::with_capacity(cap_b_std + cap_b_obj);
219 let mut cones = Vec::<SupportedConeT<f64>>::with_capacity(cap_cone_std + cap_cone_obj);
220 if verboser.is_enabled(Verbosity::Trace) {
221 crate::verbosity_log!(
222 crate::diag::Verbosity::Summary,
223 "copp3_socp: allocated capacities row/col/val/b/cones <= {}/{}/{}/{}/{}",
224 cap_val_std + cap_val_obj,
225 cap_val_std + cap_val_obj,
226 cap_val_std + cap_val_obj,
227 cap_b_std + cap_b_obj,
228 cap_cone_std + cap_cone_obj
229 );
230 }
231 let s = problem
233 .robot
234 .constraints
235 .s_vec(idx_s_start, idx_s_final + 1)?;
236 let row_before_std = row.len();
237 let col_before_std = col.len();
238 let val_before_std = val.len();
239 let b_before_std = b.len();
240 let cones_before_std = cones.len();
241 clarabel_standard_constraint_topp3(
242 &problem.as_topp3_problem(),
243 &s,
244 (&mut row, &mut col, &mut val, &mut b, &mut cones),
245 num_stationary,
246 &verboser,
247 )?;
248 if verboser.is_enabled(Verbosity::Trace) {
249 crate::verbosity_log!(
250 crate::diag::Verbosity::Summary,
251 "copp3_socp: standard-constraints delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}",
252 row.len() - row_before_std,
253 col.len() - col_before_std,
254 val.len() - val_before_std,
255 b.len() - b_before_std,
256 cones.len() - cones_before_std
257 );
258 }
259 let row_before_sqrt = row.len();
262 let col_before_sqrt = col.len();
263 let val_before_sqrt = val.len();
264 let b_before_sqrt = b.len();
265 let cones_before_sqrt = cones.len();
266 let n_var_old = clarabel_sqrt_a_copp3(
267 n,
268 problem.objectives,
269 (&mut row, &mut col, &mut val, &mut b, &mut cones),
270 num_stationary,
271 );
272 if verboser.is_enabled(Verbosity::Trace) {
273 crate::verbosity_log!(
274 crate::diag::Verbosity::Summary,
275 "copp3_socp: sqrt-a stage delta row/col/val/b/cones = +{}/+{}/+{}/+{}/+{}, n_var_old={}",
276 row.len() - row_before_sqrt,
277 col.len() - col_before_sqrt,
278 val.len() - val_before_sqrt,
279 b.len() - b_before_sqrt,
280 cones.len() - cones_before_sqrt,
281 n_var_old
282 );
283 }
284 let mut q_object = Vec::<f64>::with_capacity(n_vars);
285 q_object.resize(n_var_old, 0.0);
286 let row_before_obj = row.len();
288 let col_before_obj = col.len();
289 let val_before_obj = val.len();
290 let b_before_obj = b.len();
291 let cones_before_obj = cones.len();
292 let q_before_obj = q_object.len();
293 clarabel_objective_copp3(
294 problem,
295 num_stationary,
296 (
297 &mut row,
298 &mut col,
299 &mut val,
300 &mut b,
301 &mut cones,
302 &mut q_object,
303 ),
304 )?;
305 if verboser.is_enabled(Verbosity::Trace) {
306 let (q_min, q_max) = q_object
307 .iter()
308 .fold((f64::INFINITY, f64::NEG_INFINITY), |(mn, mx), &v| {
309 (mn.min(v), mx.max(v))
310 });
311 crate::verbosity_log!(
312 crate::diag::Verbosity::Summary,
313 "copp3_socp: objective stage delta row/col/val/b/cones/q = +{}/+{}/+{}/+{}/+{}/+{}, q_range=[{}, {}]",
314 row.len() - row_before_obj,
315 col.len() - col_before_obj,
316 val.len() - val_before_obj,
317 b.len() - b_before_obj,
318 cones.len() - cones_before_obj,
319 q_object.len() - q_before_obj,
320 q_min,
321 q_max
322 );
323 }
324 if verboser.is_enabled(Verbosity::Debug) {
325 crate::verbosity_log!(
326 crate::diag::Verbosity::Summary,
327 "copp3_socp: after objective assembly row={}, col={}, val={}, b={}, cones={}, q={}",
328 row.len(),
329 col.len(),
330 val.len(),
331 b.len(),
332 cones.len(),
333 q_object.len()
334 );
335 }
336 let n_var = q_object.len();
338 let row_len = row.len();
339 let col_len = col.len();
340 let val_len = val.len();
341 let b_len = b.len();
342 let cones_len = cones.len();
343 let a_csc = CscMatrix::new_from_triplets(b.len(), n_var, row, col, val);
344 let p_object = CscMatrix::<f64>::zeros((n_var, n_var));
345 if verboser.is_enabled(Verbosity::Trace) {
346 crate::verbosity_log!(
347 crate::diag::Verbosity::Summary,
348 "copp3_socp: matrix built with m={}, n={}, A.nnz={}, P.nnz={}",
349 b.len(),
350 n_var,
351 a_csc.nnz(),
352 p_object.nnz()
353 );
354 }
355 if verboser.is_enabled(Verbosity::Summary) {
356 crate::verbosity_log!(
357 crate::diag::Verbosity::Summary,
358 "copp3_socp: ready to solve with row/col/val/b/cones = {row_len}/{col_len}/{val_len}/{b_len}/{cones_len} and n_var = {n_var}.",
359 );
360 }
361 let settings = options.clarabel_settings().clone();
363 let mut solver = DefaultSolver::<f64>::new(&p_object, &q_object, &a_csc, &b, &cones, settings)
364 .map_err(|e| CoppError::ClarabelSolverError("copp3_socp".into(), e))?;
365 solver.solve();
366 let linsolver = solver.info.linsolver.clone();
367 let solution = solver.solution;
368 if verboser.is_enabled(Verbosity::Summary) {
369 crate::verbosity_log!(
370 crate::diag::Verbosity::Summary,
371 "copp3_socp: solve done, status = {:?}, elapsed = {}.",
372 solution.status,
373 format_duration_human(verboser.elapsed())
374 );
375 }
376 if verboser.is_enabled(Verbosity::Trace) {
377 let show = solution.x.len().min(3);
378 crate::verbosity_log!(
379 crate::diag::Verbosity::Summary,
380 "copp3_socp: solution x_len={}, head={:?}",
381 solution.x.len(),
382 &solution.x[0..show]
383 );
384 }
385 let result = if options.is_allow(solution.status) {
386 Some(clarabel_to_copp3_solution(
387 &solution.x.as_slice()[0..2 * (n + 1)],
388 &s,
389 num_stationary,
390 ))
391 } else {
392 None
393 };
394 if verboser.is_enabled(Verbosity::Trace) {
395 crate::verbosity_log!(
396 crate::diag::Verbosity::Summary,
397 "copp3_socp: allow(status)={}, extracted_profile={}",
398 options.is_allow(solution.status),
399 if result.is_some() {
400 "Some(Topp3Profile)"
401 } else {
402 "None"
403 }
404 );
405 }
406 Ok(ClarabelExpertInfor3rd {
407 result,
408 solution,
409 linsolver,
410 })
411}
412
413#[inline(always)]
415fn length_xi(n: usize, num_stationary: (usize, usize)) -> usize {
416 n + 1 - num_stationary.0.max(1) - num_stationary.1.max(1)
417}
418
419#[inline(always)]
421fn skip_a_for_xi(num_stationary_start: usize) -> usize {
422 num_stationary_start.max(1)
423}
424
425fn clarabel_sqrt_a_copp3(
431 n: usize,
432 objective: &[CoppObjective],
433 constraints: ConstraintsClarabel,
434 num_stationary: (usize, usize),
435) -> usize {
436 let (row, col, val, b, cones) = constraints;
437 for obj in objective {
438 match obj {
439 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _) => {
440 let n_skip = skip_a_for_xi(num_stationary.0);
441 let len_xi = length_xi(n, num_stationary); row.extend(b.len()..b.len() + len_xi);
445 col.extend((2 * (n + 1))..(2 * (n + 1) + len_xi));
446 val.resize(val.len() + len_xi, -1.0);
447 b.resize(b.len() + len_xi, 0.0);
448 cones.push(NonnegativeConeT(len_xi));
449 row.extend(b.len()..b.len() + 3 * len_xi);
455 val.resize(val.len() + 3 * len_xi, -1.0);
456 cones.resize(cones.len() + len_xi, SecondOrderConeT(3));
457 for k in 0..len_xi {
458 col.extend([k + n_skip, k + n_skip, 2 * (n + 1) + k]);
459 b.extend([0.25, -0.25, 0.0]);
460 }
461 return 2 * (n + 1) + len_xi;
462 }
463 _ => {}
464 }
465 }
466 2 * (n + 1)
467}
468
469fn clarabel_objective_capacity_copp3<M: RobotBasic>(
471 n: usize,
472 objective: &[CoppObjective],
473 robot: &Robot<M>,
474) -> (usize, usize, usize, usize) {
475 let (mut capacity_val, mut capacity_b, mut capacity_cones, mut n_vars) =
478 if objective.iter().any(|obj| {
479 matches!(
480 obj,
481 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
482 )
483 }) {
484 (4 * n, 4 * n, n, 2 * (n + 1))
485 } else {
486 (0, 0, 0, 2 * (n + 1))
487 };
488 let dim = robot.dim();
490 for obj in objective {
491 match obj {
492 CoppObjective::Time(_) => {
493 capacity_val += 5 * n;
495 capacity_b += 4 * n;
496 capacity_cones += n;
497 n_vars += n;
498 }
499 CoppObjective::ThermalEnergy(_, _) => {
500 capacity_val += (4 + 2 * dim) * (n + 1);
502 capacity_b += (dim + 2) * (n + 1);
503 capacity_cones += n + 1;
504 n_vars += n + 1;
505 }
506 CoppObjective::TotalVariationTorque(_, _) => {
507 capacity_val += 10 * dim * n;
509 capacity_b += 2 * dim * n;
510 capacity_cones += 1;
511 n_vars += dim * n;
512 }
513 _ => {}
514 }
515 }
516 (capacity_val, capacity_b, capacity_cones, n_vars)
517}
518
519fn clarabel_objective_copp3<M: RobotTorque>(
520 problem: &Copp3Problem<M>,
521 num_stationary: (usize, usize),
522 objective_constraints: ObjConsClarabel,
523) -> Result<(), CoppError> {
524 let (row, col, val, b, cones, q_object) = objective_constraints;
525 let n = problem.a_linearization.len() - 1;
526 let s = problem
527 .robot
528 .constraints
529 .s_vec(problem.idx_s_start, problem.idx_s_start + n + 1)?;
530 let weight_a_time = if problem
531 .objectives
532 .iter()
533 .any(|obj| matches!(obj, CoppObjective::Time(_)))
534 {
535 get_weight_a_topp3(&s, num_stationary)
536 } else {
537 vec![]
538 };
539 let weight_a_torque = if problem
540 .objectives
541 .iter()
542 .any(|obj| matches!(obj, CoppObjective::ThermalEnergy(_, _)))
543 {
544 get_weight_a_copp3(&s, num_stationary)
545 } else {
546 vec![]
547 };
548 let coeffs_torque = if problem.objectives.iter().any(|obj| {
549 matches!(
550 obj,
551 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
552 )
553 }) {
554 problem.robot.torque_coeff(problem.idx_s_start, n + 1)?
556 } else {
557 (
558 DMatrix::<f64>::zeros(0, 0),
559 DMatrix::<f64>::zeros(0, 0),
560 DMatrix::<f64>::zeros(0, 0),
561 )
562 };
563 for obj in problem.objectives {
564 match obj {
565 CoppObjective::Time(weight) => {
566 if !clarabel_objective_time_copp3(
567 &s,
568 *weight,
569 &weight_a_time,
570 num_stationary,
571 (row, col, val, b, cones, q_object),
572 ) {
573 return Err(CoppError::InvalidInput(
574 "clarabel_objective_copp3".into(),
575 "Invalid Time objective".into(),
576 ));
577 }
578 }
579 CoppObjective::ThermalEnergy(weight, normalize) => {
580 if !clarabel_objective_thermal_energy_copp3(
581 &weight_a_torque,
582 *weight,
583 normalize,
584 &coeffs_torque,
585 num_stationary,
586 (row, col, val, b, cones, q_object),
587 ) {
588 return Err(CoppError::InvalidInput(
589 "clarabel_objective_copp3".into(),
590 "Invalid ThermalEnergy objective".into(),
591 ));
592 }
593 }
594 CoppObjective::TotalVariationTorque(weight, normalize) => {
595 if !clarabel_objective_tv_torque_copp3(
596 *weight,
597 normalize,
598 &coeffs_torque,
599 num_stationary,
600 (row, col, val, b, cones, q_object),
601 ) {
602 return Err(CoppError::InvalidInput(
603 "clarabel_objective_copp3".into(),
604 "Invalid TotalVariationTorque objective".into(),
605 ));
606 }
607 }
608 CoppObjective::Linear(weight, alpha, beta) => {
609 if !clarabel_objective_linear_copp3(
610 &s,
611 *weight,
612 alpha,
613 beta,
614 q_object,
615 num_stationary,
616 ) {
617 return Err(CoppError::InvalidInput(
618 "clarabel_objective_copp3".into(),
619 "Invalid Linear objective".into(),
620 ));
621 }
622 }
623 }
624 }
625 Ok(())
626}
627
628fn clarabel_objective_time_copp3(
631 s: &[f64],
632 weight: f64,
633 weight_a: &[f64],
634 num_stationary: (usize, usize),
635 objective_constraints: ObjConsClarabel,
636) -> bool {
637 if weight < 0.0 {
638 return false;
639 }
640
641 let (row, col, val, b, cones, q_object) = objective_constraints;
642 let n = s.len() - 1;
643 let len_xi = length_xi(n, num_stationary);
644 let k_skip = skip_a_for_xi(num_stationary.0);
645 let id_xi_start = 2 * (n + 1);
649 let id_eta_start = q_object.len();
650 row.extend(b.len()..(b.len() + len_xi));
653 col.extend(id_eta_start..(id_eta_start + len_xi));
654 val.resize(val.len() + len_xi, -1.0);
655 b.resize(b.len() + len_xi, 0.0);
656 cones.push(NonnegativeConeT(len_xi));
657 for k in 0..len_xi {
660 col.extend([
662 id_xi_start + k,
663 id_eta_start + k,
664 id_xi_start + k,
665 id_eta_start + k,
666 ]);
667 row.extend([b.len(), b.len(), b.len() + 1, b.len() + 1]);
668 val.extend([-1.0, -1.0, -1.0, 1.0]);
669 b.extend([0.0, 0.0, 2.0]);
670 }
671 cones.resize(cones.len() + len_xi, SecondOrderConeT(3));
672 q_object.extend(
675 weight_a
676 .iter()
677 .skip(k_skip)
678 .take(len_xi)
679 .map(|w| weight * w),
680 );
681
682 true
683}
684
685fn clarabel_objective_thermal_energy_copp3(
688 weight_a: &[f64],
689 weight: f64,
690 normalize: &[f64],
691 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
692 num_stationary: (usize, usize),
693 objective_constraints: ObjConsClarabel,
694) -> bool {
695 if weight < 0.0 {
696 return false;
697 }
698 let (row, col, val, b, cones, q_object) = objective_constraints;
699 let mut coeff_a = coeffs_torque.0.clone();
701 let mut coeff_b = coeffs_torque.1.clone();
702 let mut coeff_g = coeffs_torque.2.clone();
703 let dim = coeff_a.nrows();
704 if normalize.len() != dim {
705 return false;
706 }
707 let n = coeff_a.ncols() - 1;
708 let normalize = DVectorView::from_slice(normalize, dim);
710 for mut col in coeff_a.column_iter_mut() {
711 col.component_mul_assign(&normalize);
712 }
713 for mut col in coeff_b.column_iter_mut() {
714 col.component_mul_assign(&normalize);
715 }
716 for mut col in coeff_g.column_iter_mut() {
717 col.component_mul_assign(&normalize);
718 }
719 let k_skip = skip_a_for_xi(num_stationary.0);
721 let len_xi = length_xi(n, num_stationary);
722
723 if num_stationary.0 > 0 {
724 let col_a = coeff_a.column(num_stationary.0);
730 let col_b = coeff_b.column(num_stationary.0);
731 let col_g = coeff_g.column(num_stationary.0);
732 let col_g_0 = coeff_g.column(0);
733 let id_t = q_object.len();
739 row.extend(b.len()..(b.len() + 2));
741 col.resize(col.len() + 2, id_t);
742 row.extend(b.len()..(b.len() + 2));
743 col.resize(col.len() + 2, 2 * (n + 1));
744 val.resize(val.len() + 3, -1.0);
745 val.push(1.0);
746 b.resize(b.len() + 2, 0.0);
747 row.extend((b.len())..(b.len() + dim));
749 col.resize(col.len() + dim, num_stationary.0);
750 val.extend(col_a.iter().take(dim));
751 row.extend((b.len())..(b.len() + dim));
752 col.resize(col.len() + dim, n + 1 + num_stationary.0);
753 val.extend(col_b.iter().take(dim));
754 b.extend(
755 col_g
756 .iter()
757 .zip(col_g_0.iter())
758 .take(dim)
759 .map(|(&g, &g_0)| -(g + g_0)),
760 );
761 cones.push(SecondOrderConeT(dim + 2));
762 q_object.push(weight * weight_a[0]);
763 }
764 if num_stationary.1 > 0 {
765 let col_a = coeff_a.column(n - num_stationary.1);
771 let col_b = coeff_b.column(n - num_stationary.1);
772 let col_g = coeff_g.column(n - num_stationary.1);
773 let col_g_f = coeff_g.column(n);
774 let id_t = q_object.len();
780 row.extend(b.len()..(b.len() + 2));
782 col.resize(col.len() + 2, id_t);
783 row.extend(b.len()..(b.len() + 2));
784 col.resize(col.len() + 2, 2 * n + len_xi + 1);
785 val.resize(val.len() + 3, -1.0);
786 val.push(1.0);
787 b.resize(b.len() + 2, 0.0);
788 row.extend((b.len())..(b.len() + dim));
790 col.resize(col.len() + dim, n - num_stationary.1);
791 val.extend(col_a.iter().take(dim));
792 row.extend((b.len())..(b.len() + dim));
793 col.resize(col.len() + dim, 2 * n + 1 - num_stationary.1);
794 val.extend(col_b.iter().take(dim));
795 b.extend(
796 col_g
797 .iter()
798 .zip(col_g_f.iter())
799 .take(dim)
800 .map(|(&g, &g_f)| -(g + g_f)),
801 );
802 cones.push(SecondOrderConeT(dim + 2));
803 q_object.push(weight * weight_a[n]);
804 }
805
806 let id_t_start = q_object.len();
810 for (k, (col_a, col_b, col_g)) in izip!(
811 coeff_a.column_iter(),
812 coeff_b.column_iter(),
813 coeff_g.column_iter()
814 )
815 .skip(k_skip)
816 .take(len_xi)
817 .enumerate()
818 {
819 row.extend(b.len()..(b.len() + 2));
824 col.resize(col.len() + 2, id_t_start + k);
825 row.extend(b.len()..(b.len() + 2));
826 col.resize(col.len() + 2, 2 * (n + 1) + k);
827 val.resize(val.len() + 3, -1.0);
828 val.push(1.0);
829 b.resize(b.len() + 2, 0.0);
830 row.extend((b.len())..(b.len() + dim));
832 col.resize(col.len() + dim, k + k_skip);
833 val.extend(col_a.iter().take(dim));
834 row.extend((b.len())..(b.len() + dim));
835 col.resize(col.len() + dim, n + 1 + k + k_skip);
836 val.extend(col_b.iter().take(dim));
837 b.extend(col_g.iter().take(dim).map(|&g| -g));
838 }
839 cones.resize(cones.len() + len_xi, SecondOrderConeT(dim + 2));
840 let weight_four = 4.0 * weight;
844 q_object.extend(
845 weight_a
846 .iter()
847 .skip(k_skip)
848 .take(len_xi)
849 .map(|w| weight_four * w),
850 );
851 true
852}
853
854fn clarabel_objective_tv_torque_copp3(
857 weight: f64,
858 normalize: &[f64],
859 coeffs_torque: &(DMatrix<f64>, DMatrix<f64>, DMatrix<f64>),
860 num_stationary: (usize, usize),
861 objective_constraints: ObjConsClarabel,
862) -> bool {
863 if weight < 0.0 {
864 return false;
865 }
866 let (row, col, val, b, cones, q_object) = objective_constraints;
867 let mut coeff_a = coeffs_torque.0.clone();
870 let mut coeff_b = coeffs_torque.1.clone();
871 let mut coeff_g = coeffs_torque.2.clone();
872 let dim = coeff_a.nrows();
873 if normalize.len() != dim {
874 return false;
875 }
876 let n = coeff_a.ncols() - 1;
877 let normalize = DVectorView::from_slice(normalize, dim);
879 for mut col in coeff_a.column_iter_mut() {
880 col.component_mul_assign(&normalize);
881 }
882 for mut col in coeff_b.column_iter_mut() {
883 col.component_mul_assign(&normalize);
884 }
885 for mut col in coeff_g.column_iter_mut() {
886 col.component_mul_assign(&normalize);
887 }
888 let n_b_old = b.len();
892 if num_stationary.0 > 0 {
895 let col_a = coeff_a.column(num_stationary.0);
897 let col_b = coeff_b.column(num_stationary.0);
898 let col_g = coeff_g.column(num_stationary.0);
899 let col_g_0 = coeff_g.column(0);
900 let n_var_old = q_object.len();
901 for (i, (&v_a, &v_b, &v_g, &v_g_0)) in
902 izip!(col_a.iter(), col_b.iter(), col_g.iter(), col_g_0.iter())
903 .take(dim)
904 .enumerate()
905 {
906 row.resize(row.len() + 3, b.len());
909 col.extend([num_stationary.0, n + num_stationary.0 + 1, n_var_old + i]);
910 val.extend([v_a, v_b, -1.0]);
911 b.push(v_g - v_g_0);
912 row.resize(row.len() + 3, b.len());
914 col.extend([num_stationary.0, n + num_stationary.0 + 1, n_var_old + i]);
915 val.extend([-v_a, -v_b, -1.0]);
916 b.push(v_g_0 - v_g);
917 }
918 q_object.resize(q_object.len() + dim, weight);
919 }
920
921 if num_stationary.1 > 0 {
922 let col_a = coeff_a.column(n - num_stationary.1);
924 let col_b = coeff_b.column(n - num_stationary.1);
925 let col_g = coeff_g.column(n - num_stationary.1);
926 let col_g_f = coeff_g.column(n);
927 let n_var_old = q_object.len();
928 for (i, (&v_a, &v_b, &v_g, &v_g_f)) in
929 izip!(col_a.iter(), col_b.iter(), col_g.iter(), col_g_f.iter())
930 .take(dim)
931 .enumerate()
932 {
933 row.resize(row.len() + 3, b.len());
936 col.extend([
937 n - num_stationary.1,
938 n + n - num_stationary.1 + 1,
939 n_var_old + i,
940 ]);
941 val.extend([v_a, v_b, -1.0]);
942 b.push(v_g - v_g_f);
943 row.resize(row.len() + 3, b.len());
945 col.extend([
946 n - num_stationary.1,
947 n + n - num_stationary.1 + 1,
948 n_var_old + i,
949 ]);
950 val.extend([-v_a, -v_b, -1.0]);
951 b.push(v_g_f - v_g);
952 }
953 q_object.resize(q_object.len() + dim, weight);
954 }
955
956 for (k, ((col_a_curr, col_b_curr, col_g_curr), (col_a_next, col_b_next, col_g_next))) in izip!(
958 coeff_a.column_iter(),
959 coeff_b.column_iter(),
960 coeff_g.column_iter()
961 )
962 .tuple_windows()
963 .enumerate()
964 .skip(num_stationary.0)
965 .take(n - num_stationary.0 - num_stationary.1)
966 {
967 let n_var_old = q_object.len();
968 for (i, (&v_a_curr, &v_b_curr, &v_g_curr, &v_a_next, &v_b_next, &v_g_next)) in izip!(
969 col_a_curr.iter(),
970 col_b_curr.iter(),
971 col_g_curr.iter(),
972 col_a_next.iter(),
973 col_b_next.iter(),
974 col_g_next.iter()
975 )
976 .enumerate()
977 {
978 row.resize(row.len() + 5, b.len());
981 col.extend([k, k + 1, n + k + 1, n + k + 2, n_var_old + i]);
982 val.extend([-v_a_curr, v_a_next, -v_b_curr, v_b_next, -1.0]);
983 b.push(v_g_next - v_g_curr);
984 row.resize(row.len() + 5, b.len());
986 col.extend([k, k + 1, n + k + 1, n + k + 2, n_var_old + i]);
987 val.extend([v_a_curr, -v_a_next, v_b_curr, -v_b_next, -1.0]);
988 b.push(v_g_curr - v_g_next);
989 }
990 q_object.resize(q_object.len() + dim, weight);
991 }
992 cones.push(NonnegativeConeT(b.len() - n_b_old));
993
994 true
995}
996
997fn clarabel_objective_linear_copp3(
999 s: &[f64],
1000 weight: f64,
1001 alpha: &[f64],
1002 beta: &[f64],
1003 q_object: &mut [f64],
1004 num_stationary: (usize, usize),
1005) -> bool {
1006 if alpha.len() != s.len() || beta.len() != s.len() {
1007 return false;
1008 }
1009 let n = s.len() - 1;
1010 if num_stationary.0 > 1 {
1012 let &s_start = s.first().unwrap();
1013 let ds_start = s[num_stationary.0] - s_start;
1014 let q_n1 = &mut q_object[num_stationary.0];
1015 for (&s_k, &alpha_k, &beta_k) in izip!(s.iter(), alpha.iter(), beta.iter())
1016 .skip(1)
1017 .take(num_stationary.0 - 1)
1018 {
1019 let dsk_start = s_k - s_start;
1020 let gamma = dsk_start / ds_start;
1021 *q_n1 += weight * gamma * gamma.cbrt() * (alpha_k + beta_k / (1.5 * dsk_start));
1024 }
1025 }
1026 if num_stationary.1 > 1 {
1027 let &s_final = s.last().unwrap();
1028 let ds_final = s[n - num_stationary.1] - s_final;
1029 let q_n2 = &mut q_object[n - num_stationary.1];
1030 for (&s_k, &alpha_k, &beta_k) in izip!(s.iter(), alpha.iter(), beta.iter())
1031 .skip(1)
1032 .take(num_stationary.1 - 1)
1033 {
1034 let dsk_final = s_k - s_final;
1035 let gamma = dsk_final / ds_final;
1036 *q_n2 += weight * gamma * gamma.cbrt() * (alpha_k + beta_k / (1.5 * dsk_final));
1039 }
1040 }
1041 for (q_k, &alpha_k) in q_object
1042 .iter_mut()
1043 .zip(alpha.iter())
1044 .take(n + 1 - num_stationary.1)
1045 .skip(num_stationary.0)
1046 {
1047 *q_k += weight * alpha_k;
1048 }
1049 for (q_k, &beta_k) in q_object
1050 .iter_mut()
1051 .skip(n + 1)
1052 .zip(beta.iter())
1053 .take(n + 1 - num_stationary.1)
1054 .skip(num_stationary.0)
1055 {
1056 *q_k += weight * beta_k;
1057 }
1058
1059 true
1060}
1061
1062#[cfg(any(feature = "c", feature = "python", test))]
1065#[inline(always)]
1066fn objective_value_time_copp3(
1067 a_sqrt_down: &[f64],
1068 weight_a: &[f64],
1069 num_stationary: (usize, usize),
1070) -> f64 {
1071 let n = a_sqrt_down.len() - 1;
1072 let k_skip = skip_a_for_xi(num_stationary.0);
1073 let len_xi = length_xi(n, num_stationary);
1074 let mut objective = 0.0;
1076 for (a_sqrt_down, weight_a) in a_sqrt_down
1077 .iter()
1078 .zip(weight_a.iter())
1079 .skip(k_skip)
1080 .take(len_xi)
1081 {
1082 objective += weight_a * a_sqrt_down;
1083 }
1084 objective
1085}
1086
1087#[cfg(any(feature = "c", feature = "python", test))]
1089#[inline(always)]
1090fn objective_value_thermal_energy_copp3(
1091 a_sqrt_down: &[f64],
1092 weight_a: &[f64],
1093 num_stationary: (usize, usize),
1094 torque: &DMatrix<f64>,
1095 normalize: &[f64],
1096) -> f64 {
1097 let mut objective = 0.0;
1098 let n = a_sqrt_down.len() - 1;
1099 if num_stationary.0 > 0 {
1100 let torque_n1 = torque.column(num_stationary.0);
1103 let torque_0 = torque.column(0);
1104 let weight = weight_a[0] * a_sqrt_down[num_stationary.0];
1105 for (tau_n1, tau_0, &normalize_i) in
1106 izip!(torque_n1.iter(), torque_0.iter(), normalize.iter())
1107 {
1108 let tau_average = 0.5 * normalize_i * (tau_n1 + tau_0);
1109 objective += weight * tau_average * tau_average;
1110 }
1111 }
1112 if num_stationary.1 > 0 {
1113 let torque_n2 = torque.column(n - num_stationary.1);
1116 let torque_n = torque.column(n);
1117 let weight = weight_a[n] * a_sqrt_down[n - num_stationary.1];
1118 for (tau_n2, tau_n, &normalize_i) in
1119 izip!(torque_n2.iter(), torque_n.iter(), normalize.iter())
1120 {
1121 let tau_average = 0.5 * normalize_i * (tau_n2 + tau_n);
1122 objective += weight * tau_average * tau_average;
1123 }
1124 }
1125 let k_skip = skip_a_for_xi(num_stationary.0);
1126 let len_xi = length_xi(n, num_stationary);
1127 for (torque_k, &a_sqrt_down_k, &weight_a_k) in
1128 izip!(torque.column_iter(), a_sqrt_down.iter(), weight_a.iter())
1129 .skip(k_skip)
1130 .take(len_xi)
1131 {
1132 for (tau_k, &normalize_i) in torque_k.iter().zip(normalize.iter()) {
1133 let tau_normal = tau_k * normalize_i;
1134 objective += 4.0 * weight_a_k * tau_normal * tau_normal * a_sqrt_down_k;
1135 }
1136 }
1137
1138 objective
1139}
1140
1141#[cfg(any(feature = "c", feature = "python", test))]
1143#[inline(always)]
1144fn objective_value_tv_torque_copp3(
1145 torque: &DMatrix<f64>,
1146 normalize: &[f64],
1147 num_stationary: (usize, usize),
1148) -> f64 {
1149 let mut objective = 0.0;
1150 let n = torque.ncols() - 1;
1151 if num_stationary.0 > 0 {
1152 let torque_n1 = torque.column(num_stationary.0);
1154 let torque_0 = torque.column(0);
1155 for (tau_n1, tau_0, &normalize_i) in
1156 izip!(torque_n1.iter(), torque_0.iter(), normalize.iter())
1157 {
1158 objective += normalize_i * (tau_n1 - tau_0).abs();
1159 }
1160 }
1161 if num_stationary.1 > 0 {
1162 let torque_n2 = torque.column(n - num_stationary.1);
1164 let torque_n = torque.column(n);
1165 for (tau_n2, tau_n, &normalize_i) in
1166 izip!(torque_n2.iter(), torque_n.iter(), normalize.iter())
1167 {
1168 objective += normalize_i * (tau_n2 - tau_n).abs();
1169 }
1170 }
1171 for (torque_col_curr, torque_col_next) in torque
1173 .column_iter()
1174 .tuple_windows()
1175 .skip(num_stationary.0)
1176 .take(n - num_stationary.0 - num_stationary.1)
1177 {
1178 for (tau_k, tau_k_next, &normalize_i) in izip!(
1179 torque_col_curr.iter(),
1180 torque_col_next.iter(),
1181 normalize.iter()
1182 ) {
1183 objective += normalize_i * (tau_k_next - tau_k).abs();
1184 }
1185 }
1186
1187 objective
1188}
1189
1190#[cfg(any(feature = "c", feature = "python", test))]
1192#[inline(always)]
1193fn objective_value_linear_copp3(
1194 a_profile: &[f64],
1195 b_profile: &[f64],
1196 alpha: &[f64],
1197 beta: &[f64],
1198) -> f64 {
1199 let mut objective = 0.0;
1201 for (a_curr, alpha_curr) in a_profile.iter().zip(alpha.iter()) {
1202 objective += a_curr * alpha_curr;
1204 }
1205 for (b_curr, beta_curr) in b_profile.iter().zip(beta.iter()) {
1206 objective += b_curr * beta_curr;
1208 }
1209 objective
1210}
1211
1212#[cfg(any(feature = "c", feature = "python", test))]
1214pub(crate) fn objective_value_copp3_opt<M: RobotTorque>(
1215 problem: &Copp3Problem<M>,
1216 profile: Topp3ProfileRef<'_>,
1217) -> (f64, Vec<f64>) {
1218 let (a_profile, b_profile, num_stationary) = profile;
1219 let s = problem
1220 .robot
1221 .constraints
1222 .s_vec(problem.idx_s_start, problem.idx_s_start + a_profile.len());
1223 let Ok(s) = s else {
1224 return (f64::INFINITY, vec![0.0; problem.objectives.len()]);
1225 };
1226 if a_profile.len() != s.len() || b_profile.len() != s.len() {
1227 return (f64::INFINITY, vec![0.0; problem.objectives.len()]);
1228 }
1229 let (a_sqrt_down, weight_a_time) = if problem.objectives.iter().any(|obj| {
1230 matches!(
1231 obj,
1232 CoppObjective::Time(_) | CoppObjective::ThermalEnergy(_, _)
1233 )
1234 }) {
1235 (
1236 a_profile
1237 .iter()
1238 .map(|a| 1.0 / a.sqrt().max(1E-16))
1239 .collect(),
1240 get_weight_a_topp3(&s, num_stationary),
1241 )
1242 } else {
1243 (vec![], vec![])
1244 };
1245 let weight_a_torque = if problem
1246 .objectives
1247 .iter()
1248 .any(|obj| matches!(obj, CoppObjective::ThermalEnergy(_, _)))
1249 {
1250 get_weight_a_copp3(&s, num_stationary)
1251 } else {
1252 vec![]
1253 };
1254 let torque = if problem.objectives.iter().any(|obj| {
1255 matches!(
1256 obj,
1257 CoppObjective::ThermalEnergy(_, _) | CoppObjective::TotalVariationTorque(_, _)
1258 )
1259 }) {
1260 let torque_result =
1261 problem
1262 .robot
1263 .get_torque_with_ab(a_profile, b_profile, problem.idx_s_start);
1264 match torque_result {
1265 Ok(torque) => torque,
1266 _ => return (f64::INFINITY, vec![0.0; problem.objectives.len()]),
1267 }
1268 } else {
1269 DMatrix::<f64>::zeros(0, 0)
1270 };
1271 let mut obj_val = Vec::with_capacity(problem.objectives.len());
1272 let mut obj_val_total = 0.0;
1273 for obj in problem.objectives {
1274 match obj {
1275 CoppObjective::Time(weight) => {
1276 let obj_here =
1277 objective_value_time_copp3(&a_sqrt_down, &weight_a_time, num_stationary);
1278 obj_val.push(obj_here);
1279 obj_val_total += weight * obj_here;
1280 }
1281 CoppObjective::ThermalEnergy(weight, normalize) => {
1282 let obj_here = objective_value_thermal_energy_copp3(
1283 &a_sqrt_down,
1284 &weight_a_torque,
1285 num_stationary,
1286 &torque,
1287 normalize,
1288 );
1289 obj_val.push(obj_here);
1290 obj_val_total += weight * obj_here;
1291 }
1292 CoppObjective::TotalVariationTorque(weight, normalize) => {
1293 let obj_here = objective_value_tv_torque_copp3(&torque, normalize, num_stationary);
1294 obj_val.push(obj_here);
1295 obj_val_total += weight * obj_here;
1296 }
1297 CoppObjective::Linear(weight, alpha, beta) => {
1298 let obj_here = objective_value_linear_copp3(a_profile, b_profile, alpha, beta);
1299 obj_val.push(obj_here);
1300 obj_val_total += weight * obj_here;
1301 }
1302 }
1303 }
1304 (obj_val_total, obj_val)
1305}
1306
1307#[cfg(test)]
1308mod tests {
1309 use super::*;
1310 use crate::copp::copp2::stable::basic::{Topp2ProblemBuilder, s_to_t_topp2};
1311 use crate::copp::copp2::stable::reach_set2::ReachSet2OptionsBuilder;
1312 use crate::copp::copp2::stable::topp2_ra::topp2_ra;
1313 use crate::copp::copp3::stable::basic::{Copp3ProblemBuilder, s_to_t_topp3};
1314 use crate::copp::copp3::stable::topp3_lp::topp3_lp;
1315 use crate::copp::copp3::stable::topp3_socp::topp3_socp;
1316 use crate::copp::{ClarabelOptionsBuilder, default_clarabel_settings};
1317 use crate::path::{add_symmetric_axial_limits_for_test, lissajous_path_for_test};
1318 use crate::robot::robot_core::Robot;
1319 use std::time::Instant;
1320 use std::vec;
1321
1322 #[test]
1323 fn test_copp3_lp() -> Result<(), CoppError> {
1324 run_test_copp3_lp_repeated(1, false)
1325 }
1326
1327 #[test]
1330 #[ignore = "slow"]
1331 fn test_copp3_lp_robust() -> Result<(), CoppError> {
1332 run_test_copp3_lp_repeated(100, true)
1333 }
1334
1335 #[test]
1336 fn test_copp3_qp() -> Result<(), CoppError> {
1337 run_test_copp3_qp_repeated(1, false)
1338 }
1339
1340 #[test]
1343 #[ignore = "slow"]
1344 fn test_copp3_qp_robust() -> Result<(), CoppError> {
1345 run_test_copp3_qp_repeated(100, true)
1346 }
1347
1348 #[test]
1349 fn test_all_objectives() -> Result<(), CoppError> {
1350 run_test_all_objectives_repeated(1, false)
1351 }
1352
1353 #[test]
1361 #[ignore = "slow"]
1362 fn test_all_objectives_robust() -> Result<(), CoppError> {
1363 run_test_all_objectives_repeated(100, true)
1364 }
1365
1366 fn run_test_copp3_lp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1367 let mut tc_sum_ra = 0.0;
1368 let mut tc_sum_lp = 0.0;
1369 let mut tc_sum_copp = 0.0;
1370 let mut tf_sum_ra = 0.0;
1371 let mut tf_sum_lp = 0.0;
1372 let mut tf_sum_copp = 0.0;
1373 let mut obj_sum_lp = 0.0;
1374 let mut obj_sum_copp = 0.0;
1375
1376 for i_exp in 0..n_exp {
1377 let n: usize = 1000;
1378 let dim = 7;
1379 let mut robot = Robot::with_capacity(dim, n);
1380
1381 let mut rng = rand::rng();
1382 let (s, path, omega, phi) =
1383 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1384 robot
1385 .with_s(&s.as_view())?
1386 .with_q_from_path_3rd(&path, 0, n)?;
1387 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1388
1389 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1390 let start = Instant::now();
1392 let options_ra0 = ReachSet2OptionsBuilder::new()
1393 .lp_feas_tol(1E-9)
1394 .a_cmp_abs_tol(1E-9)
1395 .a_cmp_rel_tol(1E-9)
1396 .build()?;
1397 let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1398 let tc_ra0 = start.elapsed().as_secs_f64() * 1E3;
1399 let (tf_ra0, _) = s_to_t_topp2(s.as_slice(), &a_ra0, 0.0)?;
1400
1401 let objectives = [CoppObjective::Linear(
1402 1.0,
1403 &get_weight_a_topp3(&s.as_slice()[0..n], (1, 1))
1404 .iter()
1405 .map(|&w_a| -w_a)
1406 .collect_vec(),
1407 &vec![0.0; n],
1408 )];
1409 let copp3_problem = Copp3ProblemBuilder::new(
1410 &mut robot,
1411 &objectives,
1412 0,
1413 &a_ra0,
1414 (0.0, 0.0),
1415 (0.0, 0.0),
1416 )
1417 .build_with_linearization()?;
1418
1419 let start = Instant::now();
1421 let profile_copp = {
1422 let mut settings = default_clarabel_settings();
1423 settings.tol_gap_rel = 1E-6;
1424 let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1425 .allow_almost_solved(true)
1426 .build()?;
1427 let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1428 if let Some(result) = result {
1429 result
1430 } else {
1431 return Err(CoppError::ClarabelSolverStatus(
1432 "copp3_socp".into(),
1433 solution.status,
1434 ));
1435 }
1436 };
1437 let tc_copp = start.elapsed().as_secs_f64() * 1E3;
1438 let (tf_copp, _) = s_to_t_topp3(s.as_slice(), profile_copp.as_parts(), 0.0)?;
1440
1441 let options_lp = ClarabelOptionsBuilder::new()
1443 .allow_almost_solved(true)
1444 .build()?;
1445 let start = Instant::now();
1446 let profile_lp = topp3_lp(&copp3_problem.as_topp3_problem(), &options_lp)?;
1447 let tc_lp = start.elapsed().as_secs_f64() * 1E3;
1448 let (tf_lp, _) = s_to_t_topp3(s.as_slice(), profile_lp.as_parts(), 0.0)?;
1450
1451 let (obj_lp, _) = objective_value_copp3_opt(&copp3_problem, profile_lp.as_parts());
1452 let (obj_copp, _) = objective_value_copp3_opt(&copp3_problem, profile_copp.as_parts());
1453
1454 if flag_print_step {
1455 crate::verbosity_log!(
1456 crate::diag::Verbosity::Summary,
1457 "Exp #{}: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_copp = {:.6}, obj_lp = {:.6}, obj_copp = {:.6}",
1458 i_exp + 1,
1459 tc_ra0,
1460 tc_lp,
1461 tc_copp,
1462 tf_ra0,
1463 tf_lp,
1464 tf_copp,
1465 obj_lp,
1466 obj_copp
1467 );
1468 }
1469
1470 if (tf_copp - tf_lp).abs() > 1e-8 {
1471 crate::verbosity_log!(
1472 crate::diag::Verbosity::Summary,
1473 "omega = {omega:?}\nphi = {phi:?}"
1474 );
1475 crate::verbosity_log!(
1476 crate::diag::Verbosity::Debug,
1477 "COPP3 time optimality failed! tf_copp - tf_lp = {}",
1478 tf_copp - tf_lp
1479 );
1480 }
1481
1482 tc_sum_ra += tc_ra0;
1483 tc_sum_lp += tc_lp;
1484 tc_sum_copp += tc_copp;
1485 tf_sum_ra += tf_ra0;
1486 tf_sum_lp += tf_lp;
1487 tf_sum_copp += tf_copp;
1488 obj_sum_lp += obj_lp;
1489 obj_sum_copp += obj_copp;
1490 }
1491
1492 crate::verbosity_log!(
1493 crate::diag::Verbosity::Summary,
1494 "Average {} experiments: tc_ra = {:.4} ms, tc_lp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_lp = {:.6}, tf_copp = {:.6}, obj_lp = {:.6}, obj_copp = {:.6}",
1495 n_exp,
1496 tc_sum_ra / n_exp as f64,
1497 tc_sum_lp / n_exp as f64,
1498 tc_sum_copp / n_exp as f64,
1499 tf_sum_ra / n_exp as f64,
1500 tf_sum_lp / n_exp as f64,
1501 tf_sum_copp / n_exp as f64,
1502 obj_sum_lp / n_exp as f64,
1503 obj_sum_copp / n_exp as f64
1504 );
1505
1506 Ok(())
1507 }
1508
1509 fn run_test_copp3_qp_repeated(n_exp: usize, flag_print_step: bool) -> Result<(), CoppError> {
1510 let mut tc_sum_ra = 0.0;
1511 let mut tc_sum_qp = 0.0;
1512 let mut tc_sum_copp = 0.0;
1513 let mut tf_sum_ra = 0.0;
1514 let mut tf_sum_qp = 0.0;
1515 let mut tf_sum_copp = 0.0;
1516 let mut obj_sum_qp = 0.0;
1517 let mut obj_sum_copp = 0.0;
1518 let mut succeed = 0;
1519
1520 for i_exp in 0..n_exp {
1521 let n: usize = 1000;
1522 let dim = 7;
1523 let mut robot = Robot::with_capacity(dim, n);
1524
1525 let mut rng = rand::rng();
1526 let (s, path, omega, phi) =
1527 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1528 robot
1529 .with_s(&s.as_view())?
1530 .with_q_from_path_3rd(&path, 0, n)?;
1531 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1532
1533 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1534 let start = Instant::now();
1536 let options_ra0 = ReachSet2OptionsBuilder::new()
1537 .lp_feas_tol(1E-9)
1538 .a_cmp_abs_tol(1E-9)
1539 .a_cmp_rel_tol(1E-9)
1540 .build()?;
1541 let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1542 let tc_ra0 = start.elapsed().as_secs_f64() * 1E3;
1543 let (tf_ra0, _) = s_to_t_topp2(s.as_slice(), &a_ra0, 0.0)?;
1544
1545 let objective = [CoppObjective::Time(1.0)];
1546 let copp3_problem =
1547 Copp3ProblemBuilder::new(&mut robot, &objective, 0, &a_ra0, (0.0, 0.0), (0.0, 0.0))
1548 .build_with_linearization()?;
1549
1550 let start = Instant::now();
1552 let profile_copp = {
1553 let mut settings = default_clarabel_settings();
1554 settings.tol_gap_rel = 1E-6;
1555 let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1556 .allow_almost_solved(true)
1557 .build()?;
1558 let (result, solution) = match copp3_socp_expert(&copp3_problem, &options) {
1559 Ok(res) => res,
1560 Err(_) => {
1561 crate::verbosity_log!(
1562 crate::diag::Verbosity::Debug,
1563 "Exp #{}: Clarabel solver failed in copp3_socp_expert!",
1564 i_exp + 1
1565 );
1566 continue;
1567 }
1568 };
1569 if let Some(result) = result {
1570 result
1571 } else {
1572 return Err(CoppError::ClarabelSolverStatus(
1573 "copp3_socp".into(),
1574 solution.status,
1575 ));
1576 }
1577 };
1578 let tc_copp = start.elapsed().as_secs_f64() * 1E3;
1579 let (tf_copp, _) = s_to_t_topp3(s.as_slice(), profile_copp.as_parts(), 0.0)?;
1581
1582 let start = Instant::now();
1584 let options_qp = ClarabelOptionsBuilder::new()
1585 .allow_almost_solved(true)
1586 .build()?;
1587 let profile_qp = topp3_socp(&copp3_problem.as_topp3_problem(), &options_qp)?;
1588 let tc_qp = start.elapsed().as_secs_f64() * 1E3;
1589 let (tf_qp, _) = s_to_t_topp3(s.as_slice(), profile_qp.as_parts(), 0.0)?;
1591
1592 let (obj_qp, _) = objective_value_copp3_opt(&copp3_problem, profile_qp.as_parts());
1593 let (obj_copp, _) = objective_value_copp3_opt(&copp3_problem, profile_copp.as_parts());
1594
1595 if flag_print_step {
1596 crate::verbosity_log!(
1597 crate::diag::Verbosity::Summary,
1598 "Exp #{}: tc_ra = {:.4} ms, tc_qp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_qp = {:.6}, tf_copp = {:.6}, obj_qp = {:.6}, obj_copp = {:.6}",
1599 i_exp + 1,
1600 tc_ra0,
1601 tc_qp,
1602 tc_copp,
1603 tf_ra0,
1604 tf_qp,
1605 tf_copp,
1606 obj_qp,
1607 obj_copp
1608 );
1609 }
1610
1611 if (tf_copp - tf_qp).abs() > 1e-4 || (obj_copp - obj_qp).abs() > 1e-4 {
1612 crate::verbosity_log!(
1613 crate::diag::Verbosity::Summary,
1614 "omega = {omega:?}\nphi = {phi:?}"
1615 );
1616 crate::verbosity_log!(
1617 crate::diag::Verbosity::Debug,
1618 "COPP3 time optimality failed at Exp #{}! tf_copp - tf_qp = {}, obj_copp - obj_qp = {}",
1619 i_exp + 1,
1620 tf_copp - tf_qp,
1621 obj_copp - obj_qp
1622 );
1623 }
1624
1625 tc_sum_ra += tc_ra0;
1626 tc_sum_qp += tc_qp;
1627 tc_sum_copp += tc_copp;
1628 tf_sum_ra += tf_ra0;
1629 tf_sum_qp += tf_qp;
1630 tf_sum_copp += tf_copp;
1631 obj_sum_qp += obj_qp;
1632 obj_sum_copp += obj_copp;
1633 succeed += 1;
1634 }
1635
1636 crate::verbosity_log!(
1637 crate::diag::Verbosity::Summary,
1638 "Average {n_exp} experiments (fail {}): tc_ra = {:.4} ms, tc_qp = {:.4} ms, tc_copp = {:.4} ms, tf_ra = {:.6}, tf_qp = {:.6}, tf_copp = {:.6}, obj_qp = {:.6}, obj_copp = {:.6}",
1639 n_exp - succeed,
1640 tc_sum_ra / succeed as f64,
1641 tc_sum_qp / succeed as f64,
1642 tc_sum_copp / succeed as f64,
1643 tf_sum_ra / succeed as f64,
1644 tf_sum_qp / succeed as f64,
1645 tf_sum_copp / succeed as f64,
1646 obj_sum_qp / succeed as f64,
1647 obj_sum_copp / succeed as f64
1648 );
1649
1650 Ok(())
1651 }
1652
1653 fn run_test_all_objectives_repeated(
1654 n_exp: usize,
1655 flag_print_step: bool,
1656 ) -> Result<(), CoppError> {
1657 let mut tc_sum_case0 = 0.0;
1658 let mut tc_sum_case1 = 0.0;
1659 let mut tc_sum_case2 = 0.0;
1660 let mut tc_sum_case3 = 0.0;
1661 let mut tc_sum_case4 = 0.0;
1662 let mut obj_sum_case0 = vec![0.0; 4];
1663 let mut obj_sum_case1 = vec![0.0; 4];
1664 let mut obj_sum_case2 = vec![0.0; 4];
1665 let mut obj_sum_case3 = vec![0.0; 4];
1666 let mut obj_sum_case4 = vec![0.0; 4];
1667
1668 let mut succeed = 0;
1669
1670 for i_exp in 0..n_exp {
1671 let n: usize = 1000;
1672 let dim = 7;
1673 let mut robot = Robot::with_capacity(dim, n);
1674
1675 let mut rng = rand::rng();
1676 let (s, path, omega, phi) =
1677 lissajous_path_for_test(dim, n, &mut rng).expect("random range is valid");
1678
1679 if flag_print_step {
1680 crate::verbosity_log!(
1681 crate::diag::Verbosity::Summary,
1682 "omega = {omega:?}\nphi = {phi:?}"
1683 );
1684 }
1685 robot
1686 .with_s(&s.as_view())?
1687 .with_q_from_path_3rd(&path, 0, n)?;
1688 add_symmetric_axial_limits_for_test(&mut robot, 1.0, 1.0, Some(5.0))?;
1689
1690 let topp2_problem = Topp2ProblemBuilder::new(&robot, (0, n - 1), (0.0, 0.0)).build()?;
1691 let options_ra0 = ReachSet2OptionsBuilder::new()
1693 .lp_feas_tol(1E-9)
1694 .a_cmp_abs_tol(1E-9)
1695 .a_cmp_rel_tol(1E-9)
1696 .build()?;
1697 let a_ra0 = topp2_ra(&topp2_problem, &options_ra0)?;
1698
1699 let objectives_test = [
1701 CoppObjective::Time(1.0),
1702 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1703 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1704 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n - 1]),
1705 ];
1706
1707 let obj_case0_src = [CoppObjective::Time(1.0)];
1709 let start = Instant::now();
1710 let (a_case0, b_case0, num_stationary) = {
1711 let copp3_problem = Copp3ProblemBuilder::new(
1712 &mut robot,
1713 &obj_case0_src,
1714 0,
1715 &a_ra0,
1716 (0.0, 0.0),
1717 (0.0, 0.0),
1718 )
1719 .build_with_linearization()?;
1720 let mut settings = default_clarabel_settings();
1721 settings.tol_gap_rel = 1E-6;
1722 let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1723 .allow_almost_solved(true)
1724 .build()?;
1725 let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1726 if let Some(result) = result {
1727 result.into_parts()
1728 } else {
1729 crate::verbosity_log!(
1730 crate::diag::Verbosity::Debug,
1731 "{:?}",
1732 CoppError::ClarabelSolverStatus(
1733 "copp3_socp (case 0)".into(),
1734 solution.status,
1735 )
1736 );
1737 continue;
1738 }
1739 };
1740 let tc_copp3_case0 = start.elapsed().as_secs_f64() * 1E3;
1741 let (_, obj_case0) = {
1742 let copp3_problem = Copp3ProblemBuilder::new(
1743 &mut robot,
1744 &objectives_test,
1745 0,
1746 &a_ra0,
1747 (0.0, 0.0),
1748 (0.0, 0.0),
1749 )
1750 .build_with_linearization()?;
1751 objective_value_copp3_opt(&copp3_problem, (&a_case0, &b_case0, num_stationary))
1752 };
1753
1754 let obj_case1 = [
1756 CoppObjective::Time(1.0),
1757 CoppObjective::ThermalEnergy(1.0, &vec![1.0; dim]),
1758 ];
1759 let start = Instant::now();
1760 let (a_case1, b_case1, num_stationary) = {
1761 let copp3_problem = Copp3ProblemBuilder::new(
1762 &mut robot,
1763 &obj_case1,
1764 0,
1765 &a_ra0,
1766 (0.0, 0.0),
1767 (0.0, 0.0),
1768 )
1769 .build_with_linearization()?;
1770 let mut settings = default_clarabel_settings();
1771 settings.tol_gap_rel = 1E-6;
1772 let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1773 .allow_almost_solved(true)
1774 .build()?;
1775 let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1776 if let Some(result) = result {
1777 result.into_parts()
1778 } else {
1779 crate::verbosity_log!(
1780 crate::diag::Verbosity::Debug,
1781 "{:?}",
1782 CoppError::ClarabelSolverStatus(
1783 "copp3_socp (case 1)".into(),
1784 solution.status,
1785 )
1786 );
1787 continue;
1788 }
1789 };
1790 let tc_copp3_case1 = start.elapsed().as_secs_f64() * 1E3;
1791 let (_, obj_case1) = {
1792 let copp3_problem = Copp3ProblemBuilder::new(
1793 &mut robot,
1794 &objectives_test,
1795 0,
1796 &a_ra0,
1797 (0.0, 0.0),
1798 (0.0, 0.0),
1799 )
1800 .build_with_linearization()?;
1801 objective_value_copp3_opt(&copp3_problem, (&a_case1, &b_case1, num_stationary))
1802 };
1803 if obj_case1[0] < obj_case0[0] - 1E-3 || obj_case1[1] - 1E-3 > obj_case0[1] {
1804 let (tf_case0, _) = s_to_t_topp2(s.as_slice(), &a_case0, 0.0)?;
1805 let (tf_case1, _) = s_to_t_topp2(s.as_slice(), &a_case1, 0.0)?;
1806 crate::verbosity_log!(
1807 crate::diag::Verbosity::Summary,
1808 "omega = {omega:?}\nphi = {phi:?}"
1809 );
1810 crate::verbosity_log!(
1811 crate::diag::Verbosity::Summary,
1812 "Case 0: obj_time = {}, obj_thermal_energy = {}, tf = {}",
1813 obj_case0[0],
1814 obj_case0[1],
1815 tf_case0
1816 );
1817 crate::verbosity_log!(
1818 crate::diag::Verbosity::Summary,
1819 "Case 1: obj_time = {}, obj_thermal_energy = {}, tf = {}",
1820 obj_case1[0],
1821 obj_case1[1],
1822 tf_case1
1823 );
1824 crate::verbosity_log!(
1825 crate::diag::Verbosity::Summary,
1826 "Interesting... Cases 0 and 1"
1827 );
1828 }
1829
1830 let obj_case2 = [
1832 CoppObjective::Time(1.0),
1833 CoppObjective::ThermalEnergy(10.0, &vec![1.0; dim]),
1834 ];
1835 let start = Instant::now();
1836 let (a_case2, b_case2, num_stationary) = {
1837 let copp3_problem = Copp3ProblemBuilder::new(
1838 &mut robot,
1839 &obj_case2,
1840 0,
1841 &a_ra0,
1842 (0.0, 0.0),
1843 (0.0, 0.0),
1844 )
1845 .build_with_linearization()?;
1846 let mut settings = default_clarabel_settings();
1847 settings.tol_gap_rel = 1E-6;
1848 let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1849 .allow_almost_solved(true)
1850 .build()?;
1851 let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1852 if let Some(result) = result {
1853 result.into_parts()
1854 } else {
1855 crate::verbosity_log!(
1856 crate::diag::Verbosity::Debug,
1857 "{:?}",
1858 CoppError::ClarabelSolverStatus(
1859 "copp3_socp (case 2)".into(),
1860 solution.status,
1861 )
1862 );
1863 continue;
1864 }
1865 };
1866 let tc_copp3_case2 = start.elapsed().as_secs_f64() * 1E3;
1867 let (_, obj_case2) = {
1868 let copp3_problem = Copp3ProblemBuilder::new(
1869 &mut robot,
1870 &objectives_test,
1871 0,
1872 &a_ra0,
1873 (0.0, 0.0),
1874 (0.0, 0.0),
1875 )
1876 .build_with_linearization()?;
1877 objective_value_copp3_opt(&copp3_problem, (&a_case2, &b_case2, num_stationary))
1878 };
1879 if obj_case2[0] < obj_case1[0] - 1E-3 || obj_case2[1] - 1E-3 > obj_case1[1] {
1880 crate::verbosity_log!(
1881 crate::diag::Verbosity::Summary,
1882 "omega = {omega:?}\nphi = {phi:?}"
1883 );
1884 crate::verbosity_log!(
1885 crate::diag::Verbosity::Summary,
1886 "Case 1: obj_time = {}, obj_thermal_energy = {}",
1887 obj_case1[0],
1888 obj_case1[1]
1889 );
1890 crate::verbosity_log!(
1891 crate::diag::Verbosity::Summary,
1892 "Case 2: obj_time = {}, obj_thermal_energy = {}",
1893 obj_case2[0],
1894 obj_case2[1]
1895 );
1896 crate::verbosity_log!(
1897 crate::diag::Verbosity::Summary,
1898 "Interesting... Cases 1 and 2"
1899 );
1900 }
1901
1902 let obj_case3 = [
1904 CoppObjective::Time(1.0),
1905 CoppObjective::TotalVariationTorque(1.0, &vec![1.0; dim]),
1906 ];
1907 let start = Instant::now();
1908 let (a_case3, b_case3, num_stationary) = {
1909 let copp3_problem = Copp3ProblemBuilder::new(
1910 &mut robot,
1911 &obj_case3,
1912 0,
1913 &a_ra0,
1914 (0.0, 0.0),
1915 (0.0, 0.0),
1916 )
1917 .build_with_linearization()?;
1918 let mut settings = default_clarabel_settings();
1919 settings.tol_gap_rel = 1E-6;
1920 let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1921 .allow_almost_solved(true)
1922 .build()?;
1923 let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1924 if let Some(result) = result {
1925 result.into_parts()
1926 } else {
1927 crate::verbosity_log!(
1928 crate::diag::Verbosity::Debug,
1929 "{:?}",
1930 CoppError::ClarabelSolverStatus(
1931 "copp3_socp (case 3)".into(),
1932 solution.status,
1933 )
1934 );
1935 continue;
1936 }
1937 };
1938 let tc_copp3_case3 = start.elapsed().as_secs_f64() * 1E3;
1939 let (_, obj_case3) = {
1940 let copp3_problem = Copp3ProblemBuilder::new(
1941 &mut robot,
1942 &objectives_test,
1943 0,
1944 &a_ra0,
1945 (0.0, 0.0),
1946 (0.0, 0.0),
1947 )
1948 .build_with_linearization()?;
1949 objective_value_copp3_opt(&copp3_problem, (&a_case3, &b_case3, num_stationary))
1950 };
1951 if obj_case3[1] < obj_case1[1] - 1E-3 || obj_case3[2] - 1E-3 > obj_case1[2] {
1952 crate::verbosity_log!(
1953 crate::diag::Verbosity::Summary,
1954 "omega = {omega:?}\nphi = {phi:?}"
1955 );
1956 crate::verbosity_log!(
1957 crate::diag::Verbosity::Summary,
1958 "Case 1: obj_time = {}, obj_thermal_energy = {}, obj_total_variation_torque = {}",
1959 obj_case1[0],
1960 obj_case1[1],
1961 obj_case1[2]
1962 );
1963 crate::verbosity_log!(
1964 crate::diag::Verbosity::Summary,
1965 "Case 3: obj_time = {}, obj_thermal_energy = {}, obj_total_variation_torque = {}",
1966 obj_case3[0],
1967 obj_case3[1],
1968 obj_case3[2]
1969 );
1970 crate::verbosity_log!(
1971 crate::diag::Verbosity::Summary,
1972 "Interesting... Cases 1 and 3"
1973 );
1974 }
1975
1976 let obj_case4 = [
1978 CoppObjective::Time(1.0),
1979 CoppObjective::Linear(1.0, &vec![0.0; n], &vec![1.0; n]),
1980 ];
1981 let start = Instant::now();
1982 let (a_case4, b_case4, num_stationary) = {
1983 let copp3_problem = Copp3ProblemBuilder::new(
1984 &mut robot,
1985 &obj_case4,
1986 0,
1987 &a_ra0,
1988 (0.0, 0.0),
1989 (0.0, 0.0),
1990 )
1991 .build_with_linearization()?;
1992 let mut settings = default_clarabel_settings();
1993 settings.tol_gap_rel = 1E-6;
1994 let options = ClarabelOptionsBuilder::with_clarabel_setting(settings)
1995 .allow_almost_solved(true)
1996 .build()?;
1997 let (result, solution) = copp3_socp_expert(&copp3_problem, &options)?;
1998 if let Some(result) = result {
1999 result.into_parts()
2000 } else {
2001 crate::verbosity_log!(
2002 crate::diag::Verbosity::Debug,
2003 "{:?}",
2004 CoppError::ClarabelSolverStatus(
2005 "copp3_socp (case 4)".into(),
2006 solution.status,
2007 )
2008 );
2009 continue;
2010 }
2011 };
2012 let tc_copp3_case4 = start.elapsed().as_secs_f64() * 1E3;
2013 let (_, obj_case4) = {
2014 let copp3_problem = Copp3ProblemBuilder::new(
2015 &mut robot,
2016 &objectives_test,
2017 0,
2018 &a_ra0,
2019 (0.0, 0.0),
2020 (0.0, 0.0),
2021 )
2022 .build_with_linearization()?;
2023 objective_value_copp3_opt(&copp3_problem, (&a_case4, &b_case4, num_stationary))
2024 };
2025 if obj_case4[1] < obj_case1[1] - 1E-3 || obj_case4[3] - 1E-3 > obj_case1[3] {
2026 crate::verbosity_log!(
2027 crate::diag::Verbosity::Summary,
2028 "omega = {omega:?}\nphi = {phi:?}"
2029 );
2030 crate::verbosity_log!(
2031 crate::diag::Verbosity::Summary,
2032 "Case 1: obj_time = {}, obj_thermal_energy = {}, obj_linear = {}",
2033 obj_case1[0],
2034 obj_case1[1],
2035 obj_case1[3]
2036 );
2037 crate::verbosity_log!(
2038 crate::diag::Verbosity::Summary,
2039 "Case 4: obj_time = {}, obj_thermal_energy = {}, obj_linear = {}",
2040 obj_case4[0],
2041 obj_case4[1],
2042 obj_case4[3]
2043 );
2044 crate::verbosity_log!(
2045 crate::diag::Verbosity::Summary,
2046 "Interesting... Cases 1 and 4"
2047 );
2048 }
2049 if obj_case4[2] < obj_case2[2] - 1E-3 || obj_case4[3] - 1E-3 > obj_case2[3] {
2050 crate::verbosity_log!(
2051 crate::diag::Verbosity::Summary,
2052 "omega = {omega:?}\nphi = {phi:?}"
2053 );
2054 crate::verbosity_log!(
2055 crate::diag::Verbosity::Summary,
2056 "Case 2: obj_time = {}, obj_total_variation_torque = {}, obj_linear = {}",
2057 obj_case2[0],
2058 obj_case2[2],
2059 obj_case2[3]
2060 );
2061 crate::verbosity_log!(
2062 crate::diag::Verbosity::Summary,
2063 "Case 4: obj_time = {}, obj_total_variation_torque = {}, obj_linear = {}",
2064 obj_case4[0],
2065 obj_case4[2],
2066 obj_case4[3]
2067 );
2068 crate::verbosity_log!(
2069 crate::diag::Verbosity::Summary,
2070 "Interesting... Cases 2 and 4"
2071 );
2072 }
2073
2074 succeed += 1;
2075
2076 if flag_print_step {
2077 crate::verbosity_log!(
2078 crate::diag::Verbosity::Summary,
2079 "Exp #{}:\n Case 0: tc={:.3}ms, obj={:?}\n Case 1: tc={:.3}ms, obj={:?}\n Case 2: tc={:.3}ms, obj={:?}\n Case 3: tc={:.3}ms, obj={:?}\n Case 4: tc={:.3}ms, obj={:?}",
2080 i_exp + 1,
2081 tc_copp3_case0,
2082 obj_case0,
2083 tc_copp3_case1,
2084 obj_case1,
2085 tc_copp3_case2,
2086 obj_case2,
2087 tc_copp3_case3,
2088 obj_case3,
2089 tc_copp3_case4,
2090 obj_case4
2091 );
2092 }
2093
2094 tc_sum_case0 += tc_copp3_case0;
2095 tc_sum_case1 += tc_copp3_case1;
2096 tc_sum_case2 += tc_copp3_case2;
2097 tc_sum_case3 += tc_copp3_case3;
2098 tc_sum_case4 += tc_copp3_case4;
2099 for i in 0..obj_case0.len() {
2100 obj_sum_case0[i] += obj_case0[i];
2101 obj_sum_case1[i] += obj_case1[i];
2102 obj_sum_case2[i] += obj_case2[i];
2103 obj_sum_case3[i] += obj_case3[i];
2104 obj_sum_case4[i] += obj_case4[i];
2105 }
2106 }
2107
2108 for i in 0..4 {
2109 obj_sum_case0[i] /= n_exp as f64;
2110 obj_sum_case1[i] /= n_exp as f64;
2111 obj_sum_case2[i] /= n_exp as f64;
2112 obj_sum_case3[i] /= n_exp as f64;
2113 obj_sum_case4[i] /= n_exp as f64;
2114 }
2115
2116 crate::verbosity_log!(
2117 crate::diag::Verbosity::Summary,
2118 "Average {n_exp} experiments (fail {}):\n Case 0: tc={:.3}ms, obj={obj_sum_case0:?}\n Case 1: tc={:.3}ms, obj={obj_sum_case1:?}\n Case 2: tc={:.3}ms, obj={obj_sum_case2:?}\n Case 3: tc={:.3}ms, obj={obj_sum_case3:?}\n Case 4: tc={:.3}ms, obj={obj_sum_case4:?}",
2119 n_exp - succeed,
2120 tc_sum_case0 / succeed as f64,
2121 tc_sum_case1 / succeed as f64,
2122 tc_sum_case2 / succeed as f64,
2123 tc_sum_case3 / succeed as f64,
2124 tc_sum_case4 / succeed as f64
2125 );
2126
2127 Ok(())
2128 }
2129}